using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.Text;
using System.Web;
using System.Xml;
using Microsoft.SharePoint;
using PS.WebParts.Rollup.Core;

namespace PS.WebParts.Rollup
{
    class CrossSiteRollupStrategy : IRollupStrategy
    {
        private ARollupDataSourceView view;
        string contentType;

        Dictionary<Guid, SPList> listDictionary;

        public CrossSiteRollupStrategy(ARollupDataSourceView view, string ctype)
        {
            if (view == null)
                throw new ArgumentNullException("view");
            this.view = view;
            contentType = ctype;
        }

        private SPList GetList(Guid listId, SPWeb web)
        {
            if (listDictionary == null)
                listDictionary = new Dictionary<Guid, SPList>();
            SPList list;
            if (!listDictionary.ContainsKey(listId))
            {
                list = web.Lists[listId];
                listDictionary[listId] = list;
            }
            else
            {
                list = listDictionary[listId];
            }
            return list;
        }



        public DataTable GetData(SPWeb web)
        {
            try
            {
                string[] lists = view.Lists;
                List<DataTable> dtList = new List<DataTable>(lists.Length);

                string query = view.Query;
                foreach (string list in lists)
                {
                    SPSiteDataQuery siteDataQuery;

                    siteDataQuery = GetQuery(query, web, list);
                    DataTable dtResults = web.GetSiteData(siteDataQuery);

                    view.Stats.SetPostProcessingBegin();

                    DataColumnCollection columns = dtResults.Columns;
                    DataColumn idColumn = columns["ID"];
                    if (idColumn != null)
                    {
                        idColumn.ColumnName = "_ItemId";
                        idColumn.ExtendedProperties.Add(FieldInfo.ExtPropKey, SPFieldType.Integer);
                        DataColumn webIdColumn = columns["WebId"];
                        webIdColumn.ColumnName = "_WebId";
                        webIdColumn.ExtendedProperties.Add(FieldInfo.ExtPropKey, SPFieldType.Guid);
                        columns["ListId"].ColumnName = "_ListId";
                        columns["_ListId"].ExtendedProperties.Add(FieldInfo.ExtPropKey, SPFieldType.Guid);

                        FieldInfoCollection fields = view.Fields;
                        Dictionary<string, string> matches = new Dictionary<string, string>(fields.Count);
                        foreach (FieldInfo info in fields)
                        {
                            if (!string.IsNullOrEmpty(info.Match))
                            {
                                matches.Add(info.InternalName, info.Match);
                            }
                        }

                        using (SPWebCache webCache = new SPWebCache(web))
                        {
                            bool hasDates = false;
                            if (dtResults.Rows.Count > 0)
                            {
                                try
                                {
                                    DataRow row = dtResults.Rows[0];
                                    SPWeb tempWeb = webCache.Get(new Guid((string)row[webIdColumn]));
                                    //GetWeb(new Guid((string)row[webIdColumn]), web.Site, web);
                                    SPList tempList = tempWeb.Lists[new Guid((string)row["_ListId"])];
                                    SPListItem tempItem = tempList.GetItemById(Convert.ToInt32(row[idColumn]));
                                    SetFieldTypesFromItem(columns, fields, tempItem, out hasDates);
                                }
                                catch (SPException)
                                {
                                }
                            }

                            if (view.IncludeListData || view.FixLookups || matches.Count > 0 || hasDates)
                            {
                                ProcessListData(dtResults, matches, webCache);
                            }
                        }
                    }
                    dtList.Add(dtResults);
                    //dsData.Tables.Add(dtResults);
                }

                if (dtList.Count == 1)
                    return dtList[0];
                else
                {
                    return JoinProcessor.Union(dtList);
                }

                //                return dsData;
            }
            catch (UnauthorizedAccessException)
            {
                view.LogError("User has no access to web: " + web.Title);
                return new DataTable();
            }
            catch (Exception e)
            {
                view.LogError("Error while rolling up data at the following web: " + web.Title, e);
                return new DataTable();
            }
        }

        private static void SetFieldTypesFromItem(DataColumnCollection columns, FieldInfoCollection fields, SPListItem item, out bool hasDates)
        {
            hasDates = false;
            foreach (FieldInfo info in fields)
            {
                SPFieldCollection spFields = item.Fields;

                string fieldInternalName = info.InternalName;
                SPFieldType t = SPFieldType.Invalid;
                if (spFields.ContainsField(fieldInternalName) && columns.Contains(fieldInternalName))
                {
                    SPField spField = spFields.GetFieldByInternalName(fieldInternalName);
                    t = spField.Type;
                }
                else
                {
                    if (info.Type != SPFieldType.Invalid)
                    {
                        t = info.Type;
                    }
                }

                if (t != SPFieldType.Invalid)
                {
                    if (t == SPFieldType.DateTime)
                        hasDates = true;

                    columns[fieldInternalName].ExtendedProperties.Add(FieldInfo.ExtPropKey, t);
                }
            }
        }

        private void ProcessListData(DataTable results, IDictionary<string, string> matches, SPWebCache cache)
        {
            bool doIncludeData = view.IncludeListData;
            bool fixLookups = view.FixLookups;
            bool convertDates = view.DatesInISO8601;

            //List<string> blackListDateCols = null;

            bool doMatchExpressions = matches != null;

            DataColumnCollection dtColumns = results.Columns;
            //if (convertDates)
            //{
            //    blackListDateCols = new List<string>(dtColumns.Count + 10);

            //    blackListDateCols.Add("_ItemId");
            //    blackListDateCols.Add("_ListId");
            //    blackListDateCols.Add("_WebId");
            //}

            if (doIncludeData)
            {
                dtColumns.Add(new DataColumn("_ListTitle", typeof(string)));
                dtColumns.Add(new DataColumn("_ListUrl", typeof(string)));
                dtColumns.Add(new DataColumn("_SiteTitle", typeof(string)));
                dtColumns.Add(new DataColumn("_SiteUrl", typeof(string)));
                dtColumns.Add(new DataColumn("_ItemUrl", typeof(string)));
                dtColumns.Add(new DataColumn("_ItemEdit", typeof(string)));

                //if (convertDates)
                //{
                //    blackListDateCols.Add("_ListTitle");
                //    blackListDateCols.Add("_ListUrl");
                //    blackListDateCols.Add("_SiteTitle");
                //    blackListDateCols.Add("_SiteUrl");
                //    blackListDateCols.Add("_ItemUrl");
                //    blackListDateCols.Add("_ItemEdit");
                //}
            }


            foreach (DataRow row in results.Rows)
            {
                if (doIncludeData)
                {
                    int id = int.Parse((row["_ItemId"]).ToString());

                    SPWeb spWeb = cache.Get(new Guid(row["_WebId"].ToString()));
                    SPList spList = GetList(new Guid(row["_ListId"].ToString()), spWeb);
                    string defaultViewUrl = spList.DefaultViewUrl;
                    bool absoluteUri = Uri.IsWellFormedUriString(view.TopLevelSite, UriKind.Absolute);
                    //Modifed 1/4/07 By HRamos
                    //Format the return url properly when working with an absolute url
                    if (absoluteUri)
                    {
                        Uri webUri = new Uri(view.TopLevelSite);
                        defaultViewUrl = webUri.Scheme + "://" + webUri.Authority + defaultViewUrl;
                    }
                    row["_ListTitle"] = spList.Title;
                    row["_ListUrl"] = defaultViewUrl;
                    row["_SiteTitle"] = spWeb.Title;
                    row["_SiteUrl"] = spWeb.Url;
                    row["_ItemId"] = id;
                    row["_ItemUrl"] = defaultViewUrl.Substring(0, defaultViewUrl.LastIndexOf('/')) +
                                      "/DispForm.aspx?ID=" + id;
                    row["_ItemEdit"] = defaultViewUrl.Substring(0, defaultViewUrl.LastIndexOf('/')) +
                                       "/EditForm.aspx?ID=" + id;
                }

                if (fixLookups || convertDates || doMatchExpressions)
                {
                    //can't use foreach if you're going to alter collection. changed to for loop --eugene
                    for (int x = 0; x < dtColumns.Count; x++)
                    {
                        DataColumn col = dtColumns[x];
                        object curValue = row[col];
                        if (curValue != null)
                        {
                            string strCurValue = curValue.ToString();
                            string strCurColName = col.ColumnName;

                            //this variable is nullable!
                            FieldInfo fieldInfo = view.Fields[strCurColName];

                            if (doMatchExpressions && matches.ContainsKey(strCurColName))
                            {
                                string match = matches[strCurColName];
                                if (!string.IsNullOrEmpty(match))
                                {
                                    row[col] = view.GetMatchValue(strCurColName, match, strCurValue);
                                    continue;
                                }
                            }

                            if (convertDates && strCurValue.Trim().Length > 0 && fieldInfo != null)
                            {
                                object objColType = col.ExtendedProperties[FieldInfo.ExtPropKey];
                                bool isExplicitDateColumn = objColType != null && (SPFieldType)objColType == SPFieldType.DateTime;

                                if (isExplicitDateColumn
                                    //|| (fieldInfo.Type == SPFieldType.Invalid
                                    //    && !blackListDateCols.Contains(strCurColName)
                                    //    )
                                    )
                                {
                                    DateTime dateTimeVal;
                                    bool res = DateTime.TryParse(strCurValue, out dateTimeVal);

                                    if (res)
                                    {
                                        row[col] = XmlConvert.ToString(dateTimeVal, XmlDateTimeSerializationMode.Unspecified); //SPUtility.CreateISO8601DateTimeFromSystemDateTime(dateTimeVal);
                                    }
                                    else
                                    {
                                        //if (isExplicitDateColumn)
                                        //    throw new FormatException("Unrecognized date: " + strCurValue);
                                        //blackListDateCols.Add(strCurColName);
                                    }
                                }
                            }

                            if (fixLookups && strCurValue != null && strCurValue.Contains(";#"))
                            {
                                string val = strCurValue;
                                row[col] = val.Substring(val.IndexOf(";#") + 2);

                                string lookupIdColumn = strCurColName + "_ID";

                                if (!dtColumns.Contains(lookupIdColumn))
                                {
                                    dtColumns.Add(new DataColumn(lookupIdColumn, typeof(string)));
                                }

                                string lookupId = val.Remove(val.IndexOf(";#"));

                                //int outputed;

                                //if (int.TryParse(lookupId, out outputed))
                                //    row[lookupIdColumn] = outputed;
                                row[lookupIdColumn] = lookupId;
                            }
                        }
                    }
                }
            }
        }

        private SPSiteDataQuery GetQuery(string str, SPWeb web, string list)
        {

            SPSiteDataQuery siteDataQuery = new SPSiteDataQuery();

            if (string.IsNullOrEmpty(str) && !string.IsNullOrEmpty(contentType))
            {
                str = string.Format("<Where><Eq><FieldRef Name='ContentType' /><Value Type='Text'>{0}</Value></Eq></Where>", contentType);
            }
            else
            {
                if (str.Contains("&lt;") && str.Contains("&gt;"))
                {
                    str = HttpUtility.HtmlDecode(str);
                }

                str = str.TrimStart('\r', '\n', '\t');
                str = str.TrimEnd('\r', '\n', '\t');

                if (!str.Contains("<Where>")
                      && !str.StartsWith("<OrderBy>", true, CultureInfo.InvariantCulture))
                {
                    str = "<Where>" + str + "</Where>";
                }


                str = ARollupDataSourceView.ReplaceQueryLiveFields(str, web, null, null, view.Parameters);
            }

            siteDataQuery.Query = str;

            uint? limit = view.QueryRowLimit;
            if (limit != null)
            {
                siteDataQuery.RowLimit = (uint)limit;
            }


            if (list != "*")
            {
                int val;
                int.TryParse(list, out val);
                if (val > 99)
                    siteDataQuery.Lists = "<Lists MaxListLimit=\"0\" Hidden=\"TRUE\" ServerTemplate=\"" + val + "\" />";
                else
                    if (val >= 0)
                        siteDataQuery.Lists = "<Lists MaxListLimit=\"0\" Hidden=\"TRUE\" BaseType=\"" + val + "\" />";

            }
            else
                siteDataQuery.Lists = "<Lists MaxListLimit=\"0\" Hidden=\"TRUE\" BaseType=\"0\" />";

            StringBuilder sb = new StringBuilder();

            //if (IncludeListData)
            //{
            //    sb.Append("<ProjectProperty Name=\"Title\" /><ListProperty Name=\"Title\" />");
            //}

            foreach (FieldInfo field in view.Fields)
            {
                sb.AppendFormat("<FieldRef Name=\"{0}\" />", field.GetCamlName());
            }
            siteDataQuery.ViewFields = sb.ToString();

            RollUpQueryWebScope scope = view.Scope;
            if (scope != RollUpQueryWebScope.Web)
                siteDataQuery.Webs = "<Webs Scope=\"" + Enum.GetName(typeof(RollUpQueryWebScope), scope) + "\" />";
            else
            {
                siteDataQuery.Webs = "<Webs Scope=\"Recursive\"><Web ID=\"" + web.ID.ToString("B").ToUpper() + "\" /></Webs>";
            }

            return siteDataQuery;
        }
    }
}
